# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from __future__ import annotations

import abc
import argparse
import dataclasses
import datetime as dt
import logging
import re
import time
from typing import Final, Iterator

from crossbench.parse import DurationParser, NumberParser


class PlaybackController(abc.ABC):
  _PERIODIC_RE: Final[re.Pattern] = re.compile(
      r"(?P<count>\d+)x every (?P<period>.+)")

  @classmethod
  def parse(cls, value: str) -> PlaybackController:
    if not value or value == "once":
      return cls.once()
    if value in ("inf", "infinity", "forever"):
      return cls.forever()
    if match := cls._PERIODIC_RE.match(value):
      count = NumberParser.positive_int(match.group("count"), "Repeat-count")
      period = DurationParser.positive_duration(match.group("period"))
      return cls.periodic(count, period)
    if value[-1].isnumeric():
      raise argparse.ArgumentTypeError(
          f"Missing unit suffix: '{value}'\n"
          "Use 'x' for repetitions or time unit 's', 'm', 'h'")
    if value[-1] == "x":
      loops = NumberParser.positive_int(value[:-1], "Repeat-count")
      return cls.repeat(loops)
    duration = DurationParser.positive_duration(value)
    return cls.timeout(duration)

  @classmethod
  def default(cls) -> PlaybackController:
    return cls.once()

  @classmethod
  def once(cls) -> RepeatPlaybackController:
    return RepeatPlaybackController(1)

  @classmethod
  def repeat(cls, count: int) -> RepeatPlaybackController:
    return RepeatPlaybackController(count)

  @classmethod
  def forever(cls) -> PlaybackController:
    return ForeverPlaybackController()

  @classmethod
  def timeout(cls, duration: dt.timedelta) -> TimeoutPlaybackController:
    return TimeoutPlaybackController(duration)

  @classmethod
  def periodic(cls, count: int,
               period: dt.timedelta) -> PeriodicPlaybackController:
    return PeriodicPlaybackController(count, period)

  @abc.abstractmethod
  def __iter__(self) -> Iterator[int]:
    pass


@dataclasses.dataclass(frozen=True)
class ForeverPlaybackController(PlaybackController):

  def __iter__(self) -> Iterator[int]:
    i = 0
    while True:
      yield i
      i += 1


@dataclasses.dataclass(frozen=True)
class TimeoutPlaybackController(PlaybackController):
  duration: dt.timedelta

  def __iter__(self) -> Iterator[int]:
    end = dt.datetime.now() + self.duration
    yield 0
    if not self.duration:
      return
    i = 1
    while dt.datetime.now() <= end:
      yield i
      i += 1


@dataclasses.dataclass(frozen=True)
class RepeatPlaybackController(PlaybackController):
  count: int

  def __post_init__(self) -> None:
    NumberParser.positive_int(self.count, " page playback count")

  def __iter__(self) -> Iterator[int]:
    yield from range(self.count)


@dataclasses.dataclass(frozen=True)
class PeriodicPlaybackController(PlaybackController):
  count: int
  period: dt.timedelta

  def __post_init__(self) -> None:
    NumberParser.positive_int(self.count, " page playback count")

  def __iter__(self) -> Iterator[int]:
    next_run_time = dt.datetime.now() + self.period
    yield 0
    i = 1
    while i < self.count:
      now = dt.datetime.now()
      wait_seconds = (next_run_time - now).total_seconds()
      if wait_seconds > 0:
        time.sleep(wait_seconds)
      else:
        logging.warning("Playback: desired time target missed")
      yield i
      next_run_time += self.period
      i += 1
